﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Black.SurfaceToolkit.Tools;

namespace Black.SurfaceToolkit.Controls
{
    /// <summary>
    /// Représente un <see cref="T:System.Windows.Controls.Panel" /> à chaînage selon un <see cref="T:System.Windows.Media.PathGeometry" />.
    /// </summary>
    /// <remarks>
    /// <para>Ce contrôle dispose ses éléments enfants suivant le chemin <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Path" />. <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsPathReversed" /> indique si le chemin est inversé.</para>
    /// <para>Les éléments enfants sont chaînés à partir de <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.OffsetOrigin" />. La position relative finale est accessible via <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Distance" />.</para>
    /// <para><see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PrecedingFreeSpace" />, <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PrecedingMargin" /> et <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.FollowingMargin" /> déterminent l'espacement entre les éléments enfants.</para>
    /// <para><see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.HorizontalPathAlignment" /> et <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.VerticalPathAlignment" /> déterminent la position des éléments enfants par rapport au chemin <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Path" />.</para>
    /// <para><div class="caption" style="font-weight : bold">StringPathPanel</div></para>
    /// <para><img src="../Images/PathPanel.Normal.png" /></para>
    /// </remarks>
    [Browsable(true), Description("Dispose les éléments en chaîne")]
    public class StringPathPanel : Panel
    {
        #region Propriétées de dépendances

        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsUpdatingDistances" />.
        /// </summary>
        private static readonly DependencyPropertyKey IsUpdatingDistancesPropertyKey = DependencyProperty.RegisterReadOnly("IsUpdatingDistances", typeof(bool), typeof(StringPathPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsPathReversed" />.
        /// </summary>
        public static readonly DependencyProperty IsPathReversedProperty = DependencyProperty.Register("IsPathReversed", typeof(bool), typeof(StringPathPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.OffsetOrigin" />.
        /// </summary>
        public static readonly DependencyProperty OffsetOriginProperty = DependencyProperty.Register("OffsetOrigin", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(StringPathPanel.OnOffsetOriginChanged)), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Path" />.
        /// </summary>
        public static readonly DependencyProperty PathProperty = DependencyProperty.Register("Path", typeof(PathGeometry), typeof(StringPathPanel), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PathLength" />.
        /// </summary>
        public static readonly DependencyProperty PathLengthProperty = DependencyProperty.Register("PathLength", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(StringPathPanel.OnPathLengthChanged)), new ValidateValueCallback(StringPathPanel.IsValidDistance));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsUpdatingDistances" />.
        /// </summary>
        public static readonly DependencyProperty IsUpdatingDistancesProperty = StringPathPanel.IsUpdatingDistancesPropertyKey.DependencyProperty;

        #endregion

        #region Propriétées attachées

        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsAfterLine" />.
        /// </summary>
        private static readonly DependencyPropertyKey IsAfterLinePropertyKey = DependencyProperty.RegisterAttachedReadOnly("IsAfterLine", typeof(bool), typeof(StringPathPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsBeforeLine" />.
        /// </summary>
        private static readonly DependencyPropertyKey IsBeforeLinePropertyKey = DependencyProperty.RegisterAttachedReadOnly("IsBeforeLine", typeof(bool), typeof(StringPathPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsDistanceLocked" />.
        /// </summary>
        private static readonly DependencyPropertyKey IsDistanceLockedPropertyKey = DependencyProperty.RegisterAttachedReadOnly("IsDistanceLocked", typeof(bool), typeof(StringPathPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Offset" />.
        /// </summary>
        private static readonly DependencyPropertyKey OffsetPropertyKey = DependencyProperty.RegisterAttachedReadOnly("Offset", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsParentMeasure, new PropertyChangedCallback(StringPathPanel.OnOffsetChanged)), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Orientation" />.
        /// </summary>
        private static readonly DependencyPropertyKey OrientationPropertyKey = DependencyProperty.RegisterAttachedReadOnly("Orientation", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Position" />.
        /// </summary>
        private static readonly DependencyPropertyKey PositionPropertyKey = DependencyProperty.RegisterAttachedReadOnly("Position", typeof(int), typeof(StringPathPanel), new FrameworkPropertyMetadata(0));
        /// <summary>
        /// Clé de la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Size" />.
        /// </summary>
        private static readonly DependencyPropertyKey SizePropertyKey = DependencyProperty.RegisterAttachedReadOnly("Size", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Distance" />.
        /// </summary>
        public static readonly DependencyProperty DistanceProperty = DependencyProperty.RegisterAttached("Distance", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(StringPathPanel.OnDistanceChanged)), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.FollowingMargin" />.
        /// </summary>
        public static readonly DependencyProperty FollowingMarginProperty = DependencyProperty.RegisterAttached("FollowingMargin", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(StringPathPanel.OnFollowingMarginChanged)), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.FreeSpaceMode" />.
        /// </summary>
        public static readonly DependencyProperty FreeSpaceModeProperty = DependencyProperty.RegisterAttached("FreeSpaceMode", typeof(FreeSpaceMode), typeof(StringPathPanel), new FrameworkPropertyMetadata(FreeSpaceMode.Fixed, new PropertyChangedCallback(StringPathPanel.OnFreeSpaceModeChanged)));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.HorizontalPathAlignment" />.
        /// </summary>
        public static readonly DependencyProperty HorizontalPathAlignmentProperty = DependencyProperty.RegisterAttached("HorizontalPathAlignment", typeof(HorizontalAlignment), typeof(StringPathPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Center, FrameworkPropertyMetadataOptions.AffectsParentMeasure), new ValidateValueCallback(StringPathPanel.IsValidHorizontalAlignment));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsFreeSpaceNeeded" />.
        /// </summary>
        public static readonly DependencyProperty IsFreeSpaceNeededProperty = DependencyProperty.RegisterAttached("IsFreeSpaceNeeded", typeof(bool), typeof(StringPathPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PrecedingFreeSpace" />.
        /// </summary>
        public static readonly DependencyProperty PrecedingFreeSpaceProperty = DependencyProperty.RegisterAttached("PrecedingFreeSpace", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(StringPathPanel.OnPrecedingFreeSpaceChanged), new CoerceValueCallback(StringPathPanel.CoercePrecedingFreeSpace)), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PrecedingMargin" />.
        /// </summary>
        public static readonly DependencyProperty PrecedingMarginProperty = DependencyProperty.RegisterAttached("PrecedingMargin", typeof(double), typeof(StringPathPanel), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnPreviousMarginChanged)), new ValidateValueCallback(StringPathPanel.IsValidDistance));
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.VerticalPathAlignment" />.
        /// </summary>
        public static readonly DependencyProperty VerticalPathAlignmentProperty = DependencyProperty.RegisterAttached("VerticalPathAlignment", typeof(VerticalAlignment), typeof(StringPathPanel), new FrameworkPropertyMetadata(VerticalAlignment.Center, FrameworkPropertyMetadataOptions.AffectsParentMeasure), new ValidateValueCallback(StringPathPanel.IsValidVerticalAlignment));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsAfterLine" />.
        /// </summary>
        public static readonly DependencyProperty IsAfterLineProperty = StringPathPanel.IsAfterLinePropertyKey.DependencyProperty;
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsBeforeLine" />.
        /// </summary>
        public static readonly DependencyProperty IsBeforeLineProperty = StringPathPanel.IsBeforeLinePropertyKey.DependencyProperty;
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.IsDistanceLocked" />.
        /// </summary>
        public static readonly DependencyProperty IsDistanceLockedProperty = StringPathPanel.IsDistanceLockedPropertyKey.DependencyProperty;
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Offset" />.
        /// </summary>
        public static readonly DependencyProperty OffsetProperty = StringPathPanel.OffsetPropertyKey.DependencyProperty;
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Orientation" />.
        /// </summary>
        public static readonly DependencyProperty OrientationProperty = StringPathPanel.OrientationPropertyKey.DependencyProperty;
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Position" />.
        /// </summary>
        public static readonly DependencyProperty PositionProperty = StringPathPanel.PositionPropertyKey.DependencyProperty;
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Size" />.
        /// </summary>
        public static readonly DependencyProperty SizeProperty = StringPathPanel.SizePropertyKey.DependencyProperty;

        #endregion

        #region Accesseurs

        /// <summary>
        /// Obtient ou définit une valeur indiquant si le chemin est inversé.
        /// </summary>
        [Browsable(true), DefaultValue(false), Description("Valeur indiquant si le chemin est inversé"), Category("StringPathPanel")]
        public bool IsPathReversed
        {
            get
            {
                return ((bool)this.GetValue(StringPathPanel.IsPathReversedProperty));
            }
            set
            {
                this.SetValue(StringPathPanel.IsPathReversedProperty, BooleanBoxes.Box(value));
            }
        }

        /// <summary>
        /// Obtient une valeur indiquant si les distances sont en cours de mise à jour.
        /// </summary>
        [Browsable(false)]
        public bool IsUpdatingDistances
        {
            get
            {
                return (bool)this.GetValue(StringPathPanel.IsUpdatingDistancesProperty);
            }
            private set
            {
                this.SetValue(StringPathPanel.IsUpdatingDistancesPropertyKey, BooleanBoxes.Box(value));
            }
        }

        /// <summary>
        /// Obtient ou définit l'origine de <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Path" />.
        /// </summary>
        [Browsable(true), DefaultValue(0.0), Description("Origine sur la géométrie"), Category("StringPathPanel")]
        public double OffsetOrigin
        {
            get
            {
                return ((double)this.GetValue(StringPathPanel.OffsetOriginProperty));
            }
            set
            {
                this.SetValue(StringPathPanel.OffsetOriginProperty, value);
            }
        }

        /// <summary>
        /// Obtient les éléments ordonnés suivant <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Distance" />.
        /// </summary>
        [Browsable(false)]
        protected IEnumerable<UIElement> OrderedItems
        {
            get
            {
                return from oItem in this.InternalChildren.OfType<UIElement>()
                       let dDistance = StringPathPanel.GetDistance(oItem)
                       orderby dDistance ascending
                       select oItem;
            }
        }

        /// <summary>
        /// Obtient ou définit le chemin du <see cref="T:Black.SurfaceToolkit.Controls.StringPathPanel" />
        /// </summary>
        [Browsable(true), DefaultValue(null), Description("Géométrie définissant le chemin"), Category("StringPathPanel")]
        public PathGeometry Path
        {
            get
            {
                return ((PathGeometry)this.GetValue(StringPathPanel.PathProperty));
            }
            set
            {
                this.SetValue(StringPathPanel.PathProperty, value);
            }
        }

        /// <summary>
        /// Obtient ou définit la longueur du chemin.
        /// </summary>
        [Browsable(true), DefaultValue(1.0), Description("Longeur de la géométrie"), Category("StringPathPanel")]
        public double PathLength
        {
            get
            {
                return ((double)this.GetValue(StringPathPanel.PathLengthProperty));
            }
            set
            {
                this.SetValue(StringPathPanel.PathLengthProperty, value);
            }
        }

        #endregion

        #region Constructeurs

        /// <summary>
        /// Initialise une nouvelle instance de <see cref="T:Black.SurfaceToolkit.Controls.StringPathPanel" />.
        /// </summary>
        public StringPathPanel()
            : base()
        {
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Obtient la distance à l'origine de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la distance à l'origine.</param>
        /// <returns>Distance à l'origine de <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetDistance(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.DistanceProperty);
        }

        /// <summary>
        /// Obtient la marge basse de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la marge basse.</param>
        /// <returns>Marge basse de <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetFollowingMargin(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.FollowingMarginProperty);
        }

        /// <summary>
        /// Obtient le mode de fonctionnement de l'espace libre de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut le mode de fonctionnement de l'espace libre.</param>
        /// <returns>Mode de fonctionnement de l'espace libre avant <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static FreeSpaceMode GetFreeSpaceMode(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (FreeSpaceMode)p_oElement.GetValue(StringPathPanel.FreeSpaceModeProperty);
        }

        /// <summary>
        /// Obtient l'alignement horizontal de l'élément spécifié par rapport au chemin.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut l'alignement horizontal par rapport au chemin.</param>
        /// <returns>Alignement horizontal de <paramref name="p_oElement" /> par rapport au chemin.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static HorizontalAlignment GetHorizontalPathAlignment(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (HorizontalAlignment)p_oElement.GetValue(StringPathPanel.HorizontalPathAlignmentProperty);
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément spécifié est après la fin de la ligne.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut savoir s'il est après la fin de la ligne.</param>
        /// <returns>Valeur indiquant si <paramref name="p_oElement" /> est après la fin de la ligne.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static bool GetIsAfterLine(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (bool)p_oElement.GetValue(StringPathPanel.IsAfterLineProperty);
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément spécifié est avant le début de la ligne.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut savoir s'il est avant le début la ligne.</param>
        /// <returns>Valeur indiquant si <paramref name="p_oElement" /> est avant le début de la ligne.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static bool GetIsBeforeLine(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (bool)p_oElement.GetValue(StringPathPanel.IsBeforeLineProperty);
        }

        /// <summary>
        /// Obtient une valeur indiquant si la distance de l'élément spécifié est vérrouillée.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut savoir si la distance est vérrouillée.</param>
        /// <returns>Valeur indiquant si la distance d'<paramref name="p_oElement" /> est vérrouillée.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static bool GetIsDistanceLocked(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (bool)p_oElement.GetValue(StringPathPanel.IsDistanceLockedProperty);
        }

        /// <summary>
        /// Obtient une valeur indiquant si de l'espace libre est requis.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut savoir si de l'espace libre est requis.</param>
        /// <returns>Valeur indiquant si de l'espace libre est requis pour <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static bool GetIsFreeSpaceNeeded(DependencyObject p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (bool)p_oElement.GetValue(StringPathPanel.IsFreeSpaceNeededProperty);
        }

        /// <summary>
        /// Obtient le décalage de l'élément spécifié sur le chemin.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut le décalage sur le chemin.</param>
        /// <returns>Décalage sur le chemin de <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetOffset(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.OffsetProperty);
        }

        /// <summary>
        /// Obtient l'orientation (en degrés) de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut l'orientation (en degrés).</param>
        /// <returns>Orientation de <paramref name="p_oElement" /> (en degrés).</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetOrientation(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.OrientationProperty);
        }

        /// <summary>
        /// Obtient une valeur indiquant la distance en nombre d'éléments de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la distance en nombre d'éléments.</param>
        /// <returns>Distance en nombre d'éléments de <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static int GetPosition(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (int)p_oElement.GetValue(StringPathPanel.PositionProperty);
        }

        /// <summary>
        /// Obtient l'espace libre avant l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut l'espace libre antérieur.</param>
        /// <returns>Espace libre avant <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetPrecedingFreeSpace(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.PrecedingFreeSpaceProperty);
        }

        /// <summary>
        /// Obtient la marge haute de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la marge haute.</param>
        /// <returns>Marge haute de <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetPrecedingMargin(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.PrecedingMarginProperty);
        }

        /// <summary>
        /// Obtient la taille de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la taille.</param>
        /// <returns>Taille de <paramref name="p_oElement" />.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static double GetSize(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (double)p_oElement.GetValue(StringPathPanel.SizeProperty);
        }

        /// <summary>
        /// Obtient l'alignement vertical de l'élément spécifié par rapport au chemin.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut l'alignement vertical par rapport au chemin.</param>
        /// <returns>Alignement vertical de <paramref name="p_oElement" /> par rapport au chemin.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static VerticalAlignment GetVerticalPathAlignment(UIElement p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            return (VerticalAlignment)p_oElement.GetValue(StringPathPanel.VerticalPathAlignmentProperty);
        }

        /// <summary>
        /// Définit la distance à l'origine de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir la distance à l'origine.</param>
        /// <param name="p_dValue">Distance de <paramref name="p_oElement" /> à l'origine.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetDistance(UIElement p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.DistanceProperty, p_dValue);
        }

        /// <summary>
        /// Définit la marge basse de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir la marge basse.</param>
        /// <param name="p_dValue">Marge basse de <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetFollowingMargin(UIElement p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.FollowingMarginProperty, p_dValue);
        }

        /// <summary>
        /// Définit le mode de fonctionnement de l'espace libre de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir le mode de fonctionnement de l'espace libre.</param>
        /// <param name="p_eValue">Mode de fonctionnement de l'espace libre avant <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetFreeSpaceMode(UIElement p_oElement, FreeSpaceMode p_eValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.FreeSpaceModeProperty, p_eValue);
        }

        /// <summary>
        /// Définit l'alignement horizontal de l'élément spécifié par rapport au chemin.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir l'alignement horizontal par rapport au chemin.</param>
        /// <param name="p_eValue">Alignement horizontal de <paramref name="p_oElement" /> par rapport au chemin.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetHorizontalPathAlignment(UIElement p_oElement, HorizontalAlignment p_eValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.HorizontalPathAlignmentProperty, p_eValue);
        }

        /// <summary>
        /// Définit une valeur indiquant si de l'espace libre est requis pour l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir si de l'espace libre est requis.</param>
        /// <param name="p_bValue">Valeur indiquant si de l'espace libre est requis pour <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetIsFreeSpaceNeeded(DependencyObject p_oElement, bool p_bValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.IsFreeSpaceNeededProperty, BooleanBoxes.Box(p_bValue));
        }

        /// <summary>
        /// Définit l'espace libre avant l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir l'espace libre antérieur.</param>
        /// <param name="p_dValue">Espace libre avant <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetPrecedingFreeSpace(UIElement p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.PrecedingFreeSpaceProperty, p_dValue);
        }

        /// <summary>
        /// Définit la marge haute de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir la marge haute.</param>
        /// <param name="p_dValue">Marge haute de <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetPrecedingMargin(UIElement p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.PrecedingMarginProperty, p_dValue);
        }

        /// <summary>
        /// Définit l'alignement vertical de l'élément spécifié par rapport au chemin.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir l'alignement vertical par rapport au chemin.</param>
        /// <param name="p_eValue">Alignement vertical de <paramref name="p_oElement" /> par rapport au chemin.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        public static void SetVerticalPathAlignment(UIElement p_oElement, VerticalAlignment p_eValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.VerticalPathAlignmentProperty, p_eValue);
        }

        #endregion

        #region Fonctions protégées

        /// <summary>
        /// Réarrange l'élément et ses enfants.
        /// </summary>
        /// <param name="p_oArrangeSize">Taille maximum autorisée.</param>
        /// <returns>Taille utilisée.</returns>
        protected override Size ArrangeOverride(Size p_oArrangeSize)
        {
            PathGeometry oPath = this.Path;
            double dPathLength = this.PathLength;

            if (oPath != null && dPathLength != 0.0)
            {
                UIElementCollection oInternalChildren = this.InternalChildren;

                // Place les enfants

                for (int i = 0; i < oInternalChildren.Count; i++)
                {
                    UIElement oChild = oInternalChildren[i];
                    double dOffset = StringPathPanel.GetOffset(oChild) / dPathLength;

                    Point oPoint, oTangente;

                    if (this.IsPathReversed)
                        dOffset = 1.0 - dOffset;

                    oPath.GetPointAtFractionLength(dOffset, out oPoint, out oTangente);

                    VerticalAlignment eVerticalPathAlignment = StringPathPanel.GetVerticalPathAlignment(oChild);
                    HorizontalAlignment eHorizontalPathAlignment = StringPathPanel.GetHorizontalPathAlignment(oChild);

                    // Place l'origine du contrôle par rapport au chemin

                    switch (eVerticalPathAlignment)
                    {
                        case VerticalAlignment.Bottom:
                            oPoint.Y -= oChild.DesiredSize.Height;
                            break;

                        case VerticalAlignment.Center:
                            oPoint.Y -= oChild.DesiredSize.Height / 2.0;
                            break;
                    }

                    switch (eHorizontalPathAlignment)
                    {
                        case HorizontalAlignment.Right:
                            oPoint.X -= oChild.DesiredSize.Width;
                            break;

                        case HorizontalAlignment.Center:
                            oPoint.X -= oChild.DesiredSize.Width / 2.0;
                            break;
                    }

                    oChild.Arrange(new Rect(oPoint, oChild.DesiredSize));
                }
            }

            return (p_oArrangeSize);
        }

        /// <summary>
        /// Met à jour la distance de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément central des mises à jour de distances.</param>
        protected virtual void ComputeDistance(UIElement p_oElement)
        {
            if (this.IsUpdatingDistances)
                return;

            if (p_oElement == null)
                return;

            UIElementCollection oInternalChildren = this.InternalChildren;
            double dCurrentDistance = StringPathPanel.GetDistance(p_oElement);
            int iElementIndex = oInternalChildren.IndexOf(p_oElement);
            UIElement oPrecedingElement = iElementIndex > 0 ? oInternalChildren[iElementIndex - 1] : null;

            if (oPrecedingElement != null)
            {
                double dPrecedingDistance = StringPathPanel.GetDistance(oPrecedingElement);
                double dPrecedingMarginDistance = StringPathPanel.GetPrecedingMargin(p_oElement) + StringPathPanel.GetFollowingMargin(oPrecedingElement);
                double dFreeSpace = StringPathPanel.GetPrecedingFreeSpace(p_oElement);

                bool bUpdatePrecedingElement = false;

                if (!StringPathPanel.GetIsDistanceLocked(p_oElement))
                {
                    double dNewDistance = dPrecedingDistance + dPrecedingMarginDistance + dFreeSpace;
                    StringPathPanel.SetDistance(p_oElement, dNewDistance);

                    // En cas d'animation

                    bUpdatePrecedingElement = StringPathPanel.GetDistance(p_oElement) != dNewDistance;
                }

                if (!StringPathPanel.GetIsDistanceLocked(oPrecedingElement) && bUpdatePrecedingElement)
                {
                    double dNewDistance = dCurrentDistance - dPrecedingMarginDistance - dFreeSpace;
                    StringPathPanel.SetDistance(oPrecedingElement, dNewDistance);
                }
            }
        }

        /// <summary>
        /// Obtient la géométrie de découpe.
        /// </summary>
        /// <param name="p_oLayoutSlotSize">Taille d'application.</param>
        /// <returns>Géométrie de découpe.</returns>
        protected override Geometry GetLayoutClip(Size p_oLayoutSlotSize)
        {
            if (this.ClipToBounds || this.Clip != null)
                return (base.GetLayoutClip(p_oLayoutSlotSize));

            return (null);
        }

        /// <summary>
        /// Mesure l'élément et ses enfants en prévision de leur réorganisation pendant la passe <see cref="M:Black.SurfaceToolkit.Controls.StringPathPanel.ArrangeOverride(System.Windows.Size)" />.
        /// </summary>
        /// <param name="p_oConstraint">Taille limite supérieure à ne pas dépasser.</param>
        /// <returns>Taille désirée de l'élément.</returns>
        protected override Size MeasureOverride(Size p_oConstraint)
        {
            Size oResult = new Size(0.0, 0.0);
            double dPathLength = this.PathLength;
            PathGeometry dPath = this.Path;

            if (dPath != null && dPathLength != 0.0)
            {
                UIElementCollection oInternalChildren = this.InternalChildren;
                double dOffsetOrigin = this.OffsetOrigin;

                // Trouve le maximum

                for (int i = 0; i < oInternalChildren.Count; i++)
                {
                    UIElement oChild = oInternalChildren[i];

                    StringPathPanel.SetOffset(oChild, dOffsetOrigin - StringPathPanel.GetDistance(oChild));

                    double dOffset = StringPathPanel.GetOffset(oChild) / dPathLength;

                    if (this.IsPathReversed)
                        dOffset = 1.0 - dOffset;

                    Point oPoint, oTangente;
                    dPath.GetPointAtFractionLength(dOffset, out oPoint, out oTangente);

                    double dAngle = Math.Atan2(oTangente.Y, oTangente.X) * 180.0 / Math.PI;
                    StringPathPanel.SetOrientation(oChild, dAngle);

                    // Calcule la taille maximum du contrôle par rapport au chemin

                    oChild.Measure(new Size(double.MaxValue, double.MaxValue));
                }
            }

            // Garantit que l'on ne sort pas de la zone de contrainte

            oResult.Width = Math.Min(oResult.Width, p_oConstraint.Width);
            oResult.Height = Math.Min(oResult.Height, p_oConstraint.Height);

            return (p_oConstraint);
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.OffsetOrigin" /> est modifiée.
        /// </summary>
        /// <param name="p_oArgs">Données de l'événement.</param>
        protected virtual void OnOffsetOriginChanged(DependencyPropertyChangedEventArgs p_oArgs)
        {
            if (this.IsInitialized)
            {
                double dOffsetOrigin = (double)p_oArgs.NewValue;

                foreach (UIElement oChild in this.InternalChildren.OfType<UIElement>())
                {
                    double dDistance = StringPathPanel.GetDistance(oChild);
                    StringPathPanel.SetOffset(oChild, dOffsetOrigin - dDistance);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PathLength" /> est modifiée.
        /// </summary>
        /// <param name="p_oArgs">Données de l'événement.</param>
        protected virtual void OnPathLengthChanged(DependencyPropertyChangedEventArgs p_oArgs)
        {
            if (this.IsInitialized)
            {
                double dLength = (double)p_oArgs.NewValue;

                foreach (UIElement oChild in this.InternalChildren.OfType<UIElement>())
                {
                    double dOffset = StringPathPanel.GetOffset(oChild);
                    StringPathPanel.SetIsAfterLine(oChild, dOffset > dLength);
                }
            }
        }

        /// <summary>
        /// Appelée lorsqu'un élément est ajouté ou enlevé.
        /// </summary>
        /// <param name="p_oVisualAdded">Elément ajouté.</param>
        /// <param name="p_oVisualRemoved">Elément enlevé.</param>
        protected override void OnVisualChildrenChanged(DependencyObject p_oVisualAdded, DependencyObject p_oVisualRemoved)
        {
            base.OnVisualChildrenChanged(p_oVisualAdded, p_oVisualRemoved);

            if (!this.IsInitialized)
                return;

            if (p_oVisualAdded != null)
                this.ComputeDistance(p_oVisualAdded as UIElement);

            if (p_oVisualRemoved != null)
                this.UpdateDistances(this.InternalChildren.OfType<UIElement>().FirstOrDefault());
        }

        /// <summary>
        /// Met à jour les positions.
        /// </summary>
        /// <param name="p_oModifiedElement">Elément dont la distance a été modifiée.</param>
        protected virtual void UpdatePositions(UIElement p_oModifiedElement)
        {
            List<UIElement> oOrderedItems = this.OrderedItems.ToList();
            int iCountItems = oOrderedItems.Count;

            // Trouve l'index le plus proche de zéro
            int iZeroIndex = iCountItems - 1;
            for (int i = 0; i < iCountItems; i++)
            {
                UIElement oItem = oOrderedItems[i];
                double dDistance = StringPathPanel.GetDistance(oItem);

                if (dDistance >= 0.0)
                {
                    double dPreviousDistance = i <= 0 ? double.NegativeInfinity : StringPathPanel.GetDistance(oOrderedItems[i - 1]);

                    if (-dPreviousDistance < dDistance)
                        iZeroIndex = i - 1;
                    else
                        iZeroIndex = i;
                    break;
                }
            }

            // Applique les positions
            for (int i = 0; i < iCountItems; i++)
            {
                UIElement oItem = oOrderedItems[i];

                StringPathPanel.SetPosition(oItem, i - iZeroIndex);
            }
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Met à jour les distances des éléments qui entourent l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut mettre à jour les distances des éléments qui l'entourent.</param>
        private void PropagateDistance(UIElement p_oElement)
        {
            // Verrouillage de l'élément
            bool bOldIsLocked = StringPathPanel.GetIsDistanceLocked(p_oElement);
            StringPathPanel.SetIsDistanceLocked(p_oElement, true);

            // Positionnement
            double dCurrentDistance = StringPathPanel.GetDistance(p_oElement);
            StringPathPanel.SetOffset(p_oElement, OffsetOrigin - dCurrentDistance);

            UIElement oLockedElement = this.PropagateDistanceBefore(p_oElement) ?? this.PropagateDistanceAfter(p_oElement);

            // Alternative :                  
            // UIElement lockedElement = PropagateDistanceAfter(element) ?? PropagateDistanceBefore(element);

            // Devérrouillage
            StringPathPanel.SetIsDistanceLocked(p_oElement, bOldIsLocked);

            // Propriété verrouillée (peut être une animation)
            if (oLockedElement != null)
                this.PropagateDistance(oLockedElement);
        }

        /// <summary>
        /// Met à jour les distances des éléments qui suivent l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut mettre à jour les distances des éléments qui le suivent.</param>
        /// <returns>Elément bloqué, <see langword="null" /> si aucun élément n'est bloqué.</returns>
        private UIElement PropagateDistanceAfter(UIElement p_oElement)
        {
            UIElementCollection oInternalChildren = this.InternalChildren;
            int iChildrenCount = oInternalChildren.Count;

            double dCurrentDistance = StringPathPanel.GetDistance(p_oElement);
            int iElementIndex = oInternalChildren.IndexOf(p_oElement);

            // Eléments suivants
            double dNextDistance = dCurrentDistance + StringPathPanel.GetFollowingMargin(p_oElement);

            for (int i = iElementIndex + 1; i < iChildrenCount; i++)
            {
                UIElement oChild = oInternalChildren[i];

                if (oChild == null)
                    continue;

                if (StringPathPanel.GetIsDistanceLocked(oChild))
                    break;

                dNextDistance += StringPathPanel.GetPrecedingFreeSpace(oChild) + StringPathPanel.GetPrecedingMargin(oChild);

                switch (StringPathPanel.GetFreeSpaceMode(oChild))
                {
                    case FreeSpaceMode.Fixed:
                    case FreeSpaceMode.Disabled: // => Fixed && FreeSpace == 0
                        StringPathPanel.SetDistance(oChild, dNextDistance);

                        if (StringPathPanel.GetDistance(oChild) != dNextDistance)
                            return (oChild);

                        break;
                    case FreeSpaceMode.Adaptive:
                        StringPathPanel.SetPrecedingFreeSpace(oChild, StringPathPanel.GetDistance(oChild) - dNextDistance);

                        // Fin de la boucle (optimisation car PrecedingSpace à déjà absorbée le décalage)
                        i = iChildrenCount;
                        break;
                }
                dNextDistance += StringPathPanel.GetFollowingMargin(oChild);
            }

            return (null);
        }

        /// <summary>
        /// Met à jour les distances des éléments qui précédent l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut mettre à jour les distances des éléments qui le précédent.</param>
        /// <returns>Elément bloqué, <see langword="null" /> si aucun élément n'est bloqué.</returns>
        private UIElement PropagateDistanceBefore(UIElement p_oElement)
        {
            UIElementCollection oInternalChildren = this.InternalChildren;

            double dCurrentDistance = StringPathPanel.GetDistance(p_oElement);
            int iElementIndex = oInternalChildren.IndexOf(p_oElement);

            // Eléments précédents
            double dNextDistance = dCurrentDistance - StringPathPanel.GetPrecedingFreeSpace(p_oElement) - StringPathPanel.GetPrecedingMargin(p_oElement);

            for (int i = iElementIndex - 1; i >= 0; i--)
            {
                UIElement oChild = oInternalChildren[i];

                if (oChild == null)
                    continue;

                if (StringPathPanel.GetIsDistanceLocked(oChild))
                    break;

                dNextDistance -= StringPathPanel.GetFollowingMargin(oChild);

                switch (StringPathPanel.GetFreeSpaceMode(oChild))
                {
                    case FreeSpaceMode.Fixed:
                    case FreeSpaceMode.Disabled: // => Fixed && FreeSpace == 0
                        StringPathPanel.SetDistance(oChild, dNextDistance);

                        if (StringPathPanel.GetDistance(oChild) != dNextDistance)
                            return (oChild);

                        break;
                    case FreeSpaceMode.Adaptive:
                        if (i == 0)
                            break; // Pas de FreeSpace valide pour le premier élément

                        StringPathPanel.SetPrecedingFreeSpace(oChild, StringPathPanel.GetDistance(oChild) - dNextDistance);

                        // Fin de la boucle (optimisation car PrecedingSpace à déjà absorbée le décalage)
                        i = 0;
                        break;
                }

                dNextDistance -= StringPathPanel.GetPrecedingFreeSpace(oChild) + StringPathPanel.GetPrecedingMargin(oChild);
            }

            return (null);
        }

        /// <summary>
        /// Définit une valeur indiquant si l'élément spécifié est après la fin de la ligne.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir s'il est après la fin de la ligne.</param>
        /// <param name="p_bValue">Valeur indiquant si <paramref name="p_oElement" /> est après la fin de la ligne.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetIsAfterLine(UIElement p_oElement, bool p_bValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.IsAfterLinePropertyKey, BooleanBoxes.Box(p_bValue));
        }

        /// <summary>
        /// Définit une valeur indiquant si l'élément spécifié est avant le début de la ligne.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir s'il est avant le début de la ligne.</param>
        /// <param name="p_bValue">Valeur indiquant si <paramref name="p_oElement" /> est avant le début de la ligne.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetIsBeforeLine(UIElement p_oElement, bool p_bValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.IsBeforeLinePropertyKey, BooleanBoxes.Box(p_bValue));
        }

        /// <summary>
        /// Définit une valeur indiquant si la distance de l'élément spécifié est vérrouillée.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir la valeur indiquant si la distance est vérrouillée.</param>
        /// <param name="p_bValue">Valeur indiquant si la distance de <paramref name="p_oElement" /> est vérrouillée.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetIsDistanceLocked(UIElement p_oElement, bool p_bValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.IsDistanceLockedPropertyKey, BooleanBoxes.Box(p_bValue));
        }

        /// <summary>
        /// Définit le décalage de l'élément spécifié sur le chemin.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir le décalage sur le chemin.</param>
        /// <param name="p_dValue">Décalage sur le chemin de <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetOffset(UIElement p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.OffsetPropertyKey, p_dValue);
        }

        /// <summary>
        /// Définit l'orientation (en degrés) de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir l'orientation (en degrés).</param>
        /// <param name="p_dValue">Orientation de <paramref name="p_oElement" /> (en degrés).</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetOrientation(UIElement p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.OrientationPropertyKey, p_dValue);
        }

        /// <summary>
        /// Définit la distance à zéro en nombre d'éléments de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir la distance en nombre d'éléments.</param>
        /// <param name="p_iValue">Distance en nombre d'éléments de <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetPosition(UIElement p_oElement, int p_iValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.PositionPropertyKey, p_iValue);
        }

        /// <summary>
        /// Définit la taille de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut définir la taille.</param>
        /// <param name="p_dValue">Taille de <paramref name="p_oElement" />.</param>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        [AttachedPropertyBrowsableForChildren]
        private static void SetSize(DependencyObject p_oElement, double p_dValue)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            p_oElement.SetValue(StringPathPanel.SizePropertyKey, p_dValue);
        }

        /// <summary>
        /// Met à jour les distances de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut mettre à jour les distances.</param>
        private void UpdateDistances(UIElement p_oElement)
        {
            if (p_oElement == null)
                return;

            double dCurrentDistance = StringPathPanel.GetDistance(p_oElement);

            // Positionnement
            StringPathPanel.SetOffset(p_oElement, this.OffsetOrigin - dCurrentDistance);

            // Une seule mise à jour
            if (this.IsUpdatingDistances)
                return;

            this.IsUpdatingDistances = true;

            this.PropagateDistance(p_oElement);

            // Déverrouillage
            this.IsUpdatingDistances = false;

            // Mise à jour de la position des éléments
            this.UpdatePositions(p_oElement);
        }

        #endregion

        #region Fonctions statiques protégées

        /// <summary>
        /// Obtient une valeur indiquant si la valeur spécifiée est une distance valide.
        /// </summary>
        /// <param name="p_oValue">Valeur à valider.</param>
        /// <returns>Valeur indiquant si <paramref name="p_oValue" /> est une distance valide.</returns>
        protected static bool IsValidDistance(object p_oValue)
        {
            double dValue = (double)p_oValue;

            return (!(Double.IsNaN(dValue) || Double.IsInfinity(dValue)));
        }

        #endregion

        #region Fonctions statiques privées

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PrecedingFreeSpace" /> est réévaluée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oValue">Valeur à adapter.</param>
        /// <returns><paramref name="p_oValue" /> adapté.</returns>
        private static object CoercePrecedingFreeSpace(DependencyObject p_oSender, object p_oValue)
        {
            UIElement oElement = p_oSender as UIElement;
            if (oElement != null)
            {
                if (StringPathPanel.GetFreeSpaceMode(oElement) == FreeSpaceMode.Disabled)
                    return 0.0;
            }

            return p_oValue;
        }

        /// <summary>
        /// Valide un alignement horizontal sur la ligne (non <see cref="F:System.Windows.HorizontalAlignment.Stretch" />).
        /// </summary>
        /// <param name="p_oValue">Alignement horizontal sur la ligne à valider.</param>
        /// <returns>Valeur indiquant si l'alignement horizontal sur la ligne est valide (non <see cref="F:System.Windows.HorizontalAlignment.Stretch" />).</returns>
        private static bool IsValidHorizontalAlignment(object p_oValue)
        {
            HorizontalAlignment eAlignment = (HorizontalAlignment)p_oValue;

            return (eAlignment == HorizontalAlignment.Center || eAlignment == HorizontalAlignment.Left || eAlignment == HorizontalAlignment.Right);
        }

        /// <summary>
        /// Valide un alignement vertical sur la ligne (non <see cref="F:System.Windows.VerticalAlignment.Stretch" />).
        /// </summary>
        /// <param name="p_oValue">Alignement vertical sur la ligne à valider.</param>
        /// <returns>Valeur indiquant si l'alignement vertical sur la ligne est valide (non <see cref="F:System.Windows.VerticalAlignment.Stretch" />).</returns>
        private static bool IsValidVerticalAlignment(object p_oValue)
        {
            VerticalAlignment eAlignment = (VerticalAlignment)p_oValue;

            return (eAlignment == VerticalAlignment.Center || eAlignment == VerticalAlignment.Top || eAlignment == VerticalAlignment.Bottom);
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Distance" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnDistanceChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            UIElement oElement = p_oSender as UIElement;

            if (oElement != null)
            {
                StringPathPanel oVisualParent = VisualTreeHelper.GetParent(oElement) as StringPathPanel;

                if (oVisualParent != null)
                {
                    oVisualParent.UpdateDistances(oElement);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.FreeSpaceMode" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnFreeSpaceModeChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            UIElement oElement = p_oSender as UIElement;

            if (oElement != null)
            {
                if ((FreeSpaceMode)p_oArgs.NewValue == FreeSpaceMode.Disabled)
                {
                    StringPathPanel.SetPrecedingFreeSpace(oElement, 0.0);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.FollowingMargin" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnFollowingMarginChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            UIElement oElement = p_oSender as UIElement;

            if (oElement != null)
            {
                StringPathPanel oVisualParent = VisualTreeHelper.GetParent(oElement) as StringPathPanel;
                if (oVisualParent != null)
                {
                    UIElementCollection oInternalChildren = oVisualParent.InternalChildren;
                    int iFollowingElementIndex = oInternalChildren.IndexOf(oElement) + 1;

                    UIElement oFollowingElement = iFollowingElementIndex >= oInternalChildren.Count ? null : oInternalChildren[iFollowingElementIndex];

                    oVisualParent.ComputeDistance(oFollowingElement);
                }

                StringPathPanel.UpdateElementSize(oElement);
            }
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.Offset" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnOffsetChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            UIElement oElement = p_oSender as UIElement;

            if (oElement != null)
            {
                double dCurrentOffset = (double)p_oArgs.NewValue;

                StringPathPanel.SetIsBeforeLine(oElement, dCurrentOffset < 0.0);

                StringPathPanel oVisualParent = VisualTreeHelper.GetParent(oElement) as StringPathPanel;

                if (oVisualParent != null)
                {
                    StringPathPanel.SetIsAfterLine(oElement, dCurrentOffset > oVisualParent.PathLength);
                }
            }
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.OffsetOrigin" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnOffsetOriginChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            ((StringPathPanel)p_oSender).OnOffsetOriginChanged(p_oArgs);
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PathLength" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnPathLengthChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            ((StringPathPanel)p_oSender).OnPathLengthChanged(p_oArgs);
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PrecedingFreeSpace" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnPrecedingFreeSpaceChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            UIElement oElement = p_oSender as UIElement;

            if (oElement != null)
            {
                StringPathPanel oVisualParent = VisualTreeHelper.GetParent(oElement) as StringPathPanel;

                if (oVisualParent != null)
                {
                    oVisualParent.ComputeDistance(oElement);
                }

                StringPathPanel.UpdateElementSize(oElement);
            }
        }

        /// <summary>
        /// Appelée lorsque la propriété <see cref="P:Black.SurfaceToolkit.Controls.StringPathPanel.PreviousMargin" /> est modifiée.
        /// </summary>
        /// <param name="p_oSender">Source de l'événement.</param>
        /// <param name="p_oArgs">Données de l'événement.</param>
        private static void OnPreviousMarginChanged(DependencyObject p_oSender, DependencyPropertyChangedEventArgs p_oArgs)
        {
            UIElement oElement = p_oSender as UIElement;

            if (oElement != null)
            {
                StringPathPanel oVisualParent = VisualTreeHelper.GetParent(oElement) as StringPathPanel;
                if (oVisualParent != null)
                {
                    oVisualParent.ComputeDistance(oElement);
                }

                StringPathPanel.UpdateElementSize(oElement);
            }
        }

        /// <summary>
        /// Met à jour la taille de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut mettre à jour la taille.</param>
        private static void UpdateElementSize(UIElement p_oElement)
        {
            StringPathPanel.SetSize(p_oElement, StringPathPanel.GetPrecedingFreeSpace(p_oElement)
                                              + StringPathPanel.GetPrecedingMargin(p_oElement)
                                              + StringPathPanel.GetFollowingMargin(p_oElement));
        }

        #endregion
    }
}
